﻿// License: The MIT License (MIT) Copyright (c) 2011..2012 Barend Gehrels

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace GrooveSharp
{
    static class ImportITunes
    {
        public const string XML_DICT = "dict";

        private const string XML_KEY = "key";
        private const string XML_NAME = "name";
        private const string XML_SIZE = "size";
        private const string XML_RATING = "rating";
        private const string XML_TOTAL_TIME = "total time";
        private const string XML_TRACK_NUMBER = "track number";
        private const string XML_PLAY_DATE_UTC = "play date utc";
        private const string XML_PERSISTENT_ID = "persistent id";

        /// <summary>
        ///  Helper function for Linq-to-XML in combination with iTunes' XML
        /// </summary>
        private static Dictionary<string, string> iTunesDictionaryOf(XElement iTunesDict)
        {
            // Create C# dictionary of this mess.
            var result = new Dictionary<string, string>();
            XElement key = null;
            foreach (var node in iTunesDict.Elements())
            {
                if (node.Name == XML_KEY)
                {
                    key = node;
                }
                else if (key != null)
                {
                    result.Add(key.Value.ToLowerInvariant(), node.Value);
                    key = null;
                }
                else
                {
                    // Something unexpected (but not fatal), not in pattern <key/><e.g. string/>.
                    App.Log.Info("iTune XML unexpected node: " + node.ToString());
                }
            }

            return result;
        }

        private static int UpdatePersistentId(int id, Dictionary<string, string> dict)
        {
            // TODO: change by parameters or traits
            Db.Connection().Execute(String.Format("update tracks set itunes_id='{0}' where track_id={1}", dict[XML_PERSISTENT_ID], id));
            return id;
        }


        private static int FigureOutAndUpdatePersistentId(Dictionary<string, string> dict)
        {
            if (dict.ContainsKey(XML_SIZE)
                && dict.ContainsKey(XML_NAME)
                && dict.ContainsKey(XML_TOTAL_TIME)
                && dict.ContainsKey(XML_TRACK_NUMBER)
                && dict.ContainsKey(XML_PERSISTENT_ID)
                )
            {
                // Try by filesize and track number
                var ids = Db.Connection().Select<int>(String.Format("select track_id from tracks where file_size={0} and track_number={1}", dict[XML_SIZE], dict[XML_TRACK_NUMBER]));
                if (ids.Count() == 1)
                {
                    return UpdatePersistentId(ids.First(), dict);
                }
            }

            return 0;
        }

        /// <summary>
        /// Parses an XML produced by iTunes.
        /// Then it compares some elements (last playdate, rating) with the data already stored in the database.
        /// If there is something new, it inserts that.
        /// If find tracks by PersistentID (see functions above to figure them out)
        /// </summary>
        public static void Import(XDocument doc)
        {
            // Read all max-play dates into a dictionary (to check if anything should be inserted later on)
            var playDates = Db.Connection().Select<Play>("select track_id,max(play_id) as play_id,max(play_date) as play_date,max(play_duration) as play_duration from plays group by track_id")
                    .ToDictionary(item => item.TrackId, item => item.PlayDate);

            var tracksByPersistentId = Db.Connection().Select<Track>("select * from tracks where itunes_id is not null")
                    .ToDictionary(item => item.ItunesPersistentId, item => item);

            /* The iTunes XML is terrible and not XML-like:
                    <key>1069</key>
                    <dict>
                    <key>Track ID</key><integer>1069</integer>
                    <key>Name</key><string>The Man Who Sold the World</string>
                    <key>Size</key><integer>8266805</integer>
                    <key>Rating</key><integer>60</integer>
                    <key>Persistent ID</key><string>65EE2D8A41FA56DC</string>
		            </dict>
             * 
             * XML has better ways to do this :-(
             * The best we can do is put this in a C# dictionary.
             */

            var dictionaries =
                    from element
                    in doc.Descendants(XML_DICT).Descendants(XML_DICT).Descendants(XML_DICT)
                    select iTunesDictionaryOf(element);

            foreach (var dict in dictionaries)
            {
                int ratingValue;
                if (dict.ContainsKey(XML_PLAY_DATE_UTC)
                    && dict.ContainsKey(XML_RATING)
                    && dict.ContainsKey(XML_PERSISTENT_ID)
                    && Int32.TryParse(dict[XML_RATING], out ratingValue)
                    )
                {
                    int trackId = 0;
                    if (tracksByPersistentId.ContainsKey(dict[XML_PERSISTENT_ID]))
                    {
                        trackId = tracksByPersistentId[dict[XML_PERSISTENT_ID]].Id;
                    }
                    if (trackId == 0)
                    {
                        // We have to figure out the track by e.g. filesize, tracknumber, name, ...
                        trackId = FigureOutAndUpdatePersistentId(dict);
                    }

                    if (trackId != 0)
                    {
                        DateTime playDate = DateTime.Parse(dict[XML_PLAY_DATE_UTC]);
                        //App.Log.Info("Name {0}, size {1}, rating {2}, date {3} {4} ==> ID: {5}", item[XML_NAME], item[XML_SIZE], item[XML_RATING], d, d.Month, track.Id);

                        if (!playDates.ContainsKey(trackId) || playDates[trackId] < playDate)
                        {
                            Db.Connection().Insert(new Play() { TrackId = trackId, UserId = Db.UserId(), PlayDate = playDate, PlayMachine = "iPad" });
                            Db.Connection().Insert(new TrackRating() { TrackId = trackId, Value = ratingValue, RatingDate = playDate });
                        }
                    }
                }
            }
        }
    }
}
